package com.fho4565.commands;

import com.fho4565.core.timer.Timer;
import com.fho4565.core.timer.TimerList;
import com.fho4565.main.Configs;
import com.fho4565.main.Utils;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.commons.lang3.tuple.MutablePair;

import java.util.ArrayList;
import java.util.Objects;

import static com.fho4565.main.Utils.sendCdFeedback;
import static com.fho4565.main.Utils.sendTCdFeedback;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.string;
import static net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer;

@Mod.EventBusSubscriber
public class TimerCommand {
    private static final TypeToken<TimerList> type = new TypeToken<>() {
    };
    private static TimerList timers;

    private static void save() {
        Configs.timers.set(new GsonBuilder()
                .create()
                .toJson(timers));
        Configs.timers.save();
    }

    private static void read() {
        String json = Configs.timers.get();
        if (json.isEmpty()) {
            timers = new TimerList();
            return;
        }
        TimerList map = new GsonBuilder()
                .create().fromJson(json, type.getType());
        timers = Objects.requireNonNullElseGet(map, TimerList::new);
    }

    public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(
                Commands.literal("timer").requires(s -> s.hasPermission(2))
                        .then(list())
                        .then(start())
                        .then(add(dispatcher))
                        .then(modify())
                        .then(stop())
                        .then(remove())
                        .then(reset())
        );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> remove() {
        return Commands.literal("remove")
                .then(Commands.argument("name", string())
                        .suggests((context, builder) -> {
                            ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                            timers.forEach(timer -> suggestions.add(new MutablePair<>(timer.getName(), timer.getCommand())));
                            return Utils.getCommandSuggestions(builder, suggestions);
                        })
                        .executes(context -> {
                            String name = getString(context, "name");
                            if (!timers.contains(name)) {
                                return 0;
                            }
                            timers.remove(name);
                            save();
                            sendTCdFeedback(context, "mcd.com.fho4565.command.timer.remove", name);
                            return 1;
                        }));
    }
    private static LiteralArgumentBuilder<CommandSourceStack> reset(){
        return Commands.literal("reset")
                .then(Commands.argument("name", string()).suggests((context, builder) -> {
                            ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                            timers.stream().filter(Timer::isEnabled).forEach(timer -> suggestions.add(new MutablePair<>(timer.getName(), timer.getCommand())));
                            return Utils.getCommandSuggestions(builder, suggestions);
                        }).executes(context -> {
                            String name = getString(context, "name");
                            if (!timers.contains(name)) {
                                return 0;
                            }
                            timers.getByName(name).reset();
                            sendTCdFeedback(context, "mcd.com.fho4565.command.timer.reset", name);
                            return 1;
                        })
                        .then(Commands.literal("remainingTime")
                                .executes(context -> {
                                    String name = getString(context, "name");
                                    if (!timers.contains(name)) {
                                        return 0;
                                    }
                                    timers.getByName(name).setRemainingTime(timers.getByName(name).getInterval());
                                    sendTCdFeedback(context, "mcd.com.fho4565.command.timer.resetRemainingTime", name);
                                    return 1;
                                })
                        )
                );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> modify() {
        return Commands.literal("modify")
                .then(Commands.argument("name", string())
                        .suggests((context, builder) -> {
                            ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                            timers.forEach(timer -> suggestions.add(new MutablePair<>(timer.getName(), timer.getCommand())));
                            return Utils.getCommandSuggestions(builder, suggestions);
                        })
                        .then(Commands.literal("interval")
                                .then(Commands.argument("interval", IntegerArgumentType.integer(1))
                                        .executes(context -> {
                                            String name = getString(context, "name");
                                            if (!timers.contains(name)) {
                                                return 0;
                                            }
                                            timers.getByName(name).setInterval(getInteger(context, "interval"));
                                            save();
                                            sendTCdFeedback(context, "mcd.com.fho4565.command.timer.modify.interval", name);
                                            return 1;
                                        })
                                ))
                        .then(Commands.literal("command")
                                .then(Commands.argument("command", string())
                                        .executes(context -> {
                                                    String name = getString(context, "name");
                                                    if (!timers.contains(name)) {
                                                        return 0;
                                                    }
                                                    timers.getByName(name).setCommand(getString(context, "command"));
                                                    save();
                                                    sendTCdFeedback(context, "mcd.com.fho4565.command.timer.modify.command", name);
                                                    return 1;
                                                }
                                        )
                                ))
                        .then(Commands.literal("silent")
                                .then(Commands.argument("isSilent", BoolArgumentType.bool())
                                        .executes(context -> {
                                                    String name = getString(context, "name");
                                                    if (!timers.contains(name)) {
                                                        return 0;
                                                    }
                                                    timers.getByName(name).setSilent(BoolArgumentType.getBool(context,"isSilent"));
                                                    save();
                                                    if (BoolArgumentType.getBool(context,"isSilent")){
                                                        sendTCdFeedback(context, "mcd.com.fho4565.command.timer.modify.silentOn", name);
                                                    }else {
                                                        sendTCdFeedback(context, "mcd.com.fho4565.command.timer.modify.silentOff", name);
                                                    }
                                                    return 1;
                                                }
                                        )
                                ))
                );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> add(CommandDispatcher<CommandSourceStack> dispatcher) {
        return Commands.literal("add")
                .then(Commands.argument("name", string())
                        .then(Commands.argument("interval", IntegerArgumentType.integer(1))
                                .then(Commands.argument("command", string())
                                        .executes(context -> {
                                            String name = getString(context, "name");
                                            if (timers.contains(name)) {
                                                return 0;
                                            }
                                            int interval = getInteger(context, "interval");
                                            String command = getString(context, "command");
                                            timers.add(new Timer(name, interval, command));
                                            save();
                                            sendTCdFeedback(context, "mcd.com.fho4565.command.timer.add", name);
                                            return 1;
                                        })
                                )));
    }

    private static LiteralArgumentBuilder<CommandSourceStack> start() {
        return Commands.literal("start")
                .executes(context -> {
                    if (timers.isEmpty()){
                        return 0;
                    }
                    timers.forEach(timer -> timer.setEnabled(true));
                    sendTCdFeedback(context,"mcd.com.fho4565.command.timer.startAll");
                    return 1;
                })
                .then(Commands.argument("name", string())
                .suggests((context, builder) -> {
                    ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                    timers.stream().filter(Timer::isEnabled).forEach(timer->suggestions.add(new MutablePair<>(timer.getName(), timer.getCommand())));
                    return Utils.getCommandSuggestions(builder, suggestions);
                })
                .executes(context -> {
                    String name = getString(context, "name");
                    if (!timers.contains(name)) {
                        sendTCdFeedback(context, "mcd.com.fho4565.command.timer.startnull");
                        return 0;
                    }
                    timers.getByName(name).setEnabled(true);
                    sendTCdFeedback(context,"mcd.com.fho4565.command.timer.start",name);
                    return 1;
                })
        );
    }
    private static LiteralArgumentBuilder<CommandSourceStack> stop() {
        return Commands.literal("stop")
                .executes(context -> {
                    if (timers.isEmpty()){
                        return 0;
                    }
                    timers.forEach(timer -> timer.setEnabled(true));
                    sendTCdFeedback(context,"mcd.com.fho4565.command.timer.stopAll");
                    return 1;
                })
                .then(Commands.argument("name", string())
                .suggests((context, builder) -> {
                    ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                    timers.stream().filter(timer->!timer.isEnabled()).forEach(timer->suggestions.add(new MutablePair<>(timer.getName(), timer.getCommand())));
                    return Utils.getCommandSuggestions(builder, suggestions);
                })
                .executes(context -> {
                    String name = getString(context, "name");
                    if (!timers.contains(name)) {
                        sendTCdFeedback(context, "mcd.com.fho4565.command.timer.startnull");
                        return 0;
                    }
                    timers.getByName(name).setEnabled(false);
                    sendTCdFeedback(context,"mcd.com.fho4565.command.timer.stop",name);
                    return 1;
                })
        );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> list() {
        return Commands.literal("list").executes(context -> {
            if (timers.isEmpty()) {
                sendTCdFeedback(context, "mcd.com.fho4565.command.timer.listnull");
                return 1;
            }
            timers.forEach(timer -> {

                MutableComponent mutableComponent = new TranslatableComponent("mcd.com.fho4565.command.timer.list",
                        timer.getName(),
                        timer.getInterval(),
                        timer.isEnabled())
                        .withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponent(timer.getCommand()))));
                sendCdFeedback(context,mutableComponent);
            });
            return 1;
        });
    }

    @SubscribeEvent
    public static void onLoad(WorldEvent.Load event) {
        read();
    }

    @SubscribeEvent
    public static void tick(TickEvent.ServerTickEvent event) {
        if (event.phase == TickEvent.Phase.START) {
            timers.tick(getCurrentServer().createCommandSourceStack(),getCurrentServer().getCommands());
        }
    }
}
